iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
Modern Web

Follow me! 來一場前端技能樹之旅系列 第 21

[Day 21 - React] 今晚我想來點,React的其他功能

  • 分享至 

  • xImage
  •  

前情提要:在上一篇網頁UI組件化 — React component,大致了解 React 最重要的核心 — Component,並且學會了在組件內控制資料的處理,接下來就來探討 React 其他更便利的功能。

有條件的 Render Element

在某些狀況下,你可能會希望依據 State 資料狀態,來改變 Render 的內容,React 就允許使用 JavaScript 的 if 或三元運算子,控制改變要渲染的 Element。 比如我們延續上一篇文章使用的計數器範例,要在分數小於零時控制 minus_btn 不顯示,不讓使用者減少分數。

在 Counter.js 宣告一個變數 minus_btn 儲存 Element,初始值為 null,代表沒有指定 Element,顯示在畫面上就會是空,會是分數小於零不顯示 minus_btn 的情況。

Counter.js

export default function Counter() {
  ...
  let minus_btn = null;

  return (
    <div className="Counter">
      ...
    </div>
  );
}

接著要利用 if,在分數大於零的時候要指定 Element 給變數 minus_btn,並將原本 render() 內的 Element 換成 minus_btn 控制。

export default function Counter() {
  ...
  let minus_btn = null;

  if (score > 0)
    minus_btn = <button onClick={() => setScore(score - 1)}>minus</button>;

  return (
    <div className="Counter">
      ...
      <button onClick={() => setScore(score + 1)}>Plus</button>
      {minus_btn}
    </div>
  );
}

這樣就完成條件 Render 了

另外你還可以換成用三元運算子條件 ? (條件為 true 時的值) : (條件為 false 時的值),直接在 render() 內改變 Element 的顯示。

export default function Counter() {
  ...

  return (
    <div className="Counter">
      ...
      <button onClick={() => setScore(score + 1)}>Plus</button>
      {score > 0 ? <button onClick={() => setScore(score - 1)}>minus</button> : null}
    </div>
  );
}

Render Lists

目前我們是直接複製 Member Component 產生三個成員,假設成員數目增加,這種複製的做法就顯得冗長又沒效率。所以可以使用 JavaScript 的陣列處理方法 map(),該方法會合併陣列每一個元素回傳的運算結果,建立一個新的陣列。

遍歷資料 members 內部的每個成員 member,生成 Member Component 並代入名字資料,再將所有回傳的結果組合新陣列指定給變數 element_list,就可以直接依據資料自動 Render 多個 Component。

App.js

export default function App() {
  const members = ["May", "Julia", "Bob"];
  const element_list = members.map((member) => <Member name={member} />);

  return (
    <div className="App">
      <h1>Member Score</h1>
      {element_list}
    </div>
  );
}

但這時候你會看到 Console 視窗出現一個錯誤:Warning: Each child in list should have a unique "key" prop.,告訴我們應該為每個 <Member /> 新增一個不重複的 key

為什麼需要Key?Key 的功用在於可以透過幫助 React 來分辨哪些項目被改變、增加或刪除,進而進行畫面的更新,為了不讓 React 搞錯每個 Element 的身分,每個元素都要有一個獨立的 Key 值。所以我們就直接為 <Member /> 新增 key,指定每位成員的姓名為其值。

export default function App() {
  const members = ["May", "Julia", "Bob"];
  const element_list = members.map((member) => <Member key={member} name={member} />);

  return (
    <div className="App">
      <h1>Member Score</h1>
      {element_list}
    </div>
  );
}

之前我們有提到在 JSX 內可以放入任何表達式,前面的作法是另外宣告變數 element_list 存放列表元素,再用大括號包起來放到 JSX 中;另一個作法則是可以改成將 map() 放在 JSX 中:

return (
    <div className="App">
      ...
      {members.map((member) => (
        <Member key={member} name={member} />
      ))}
    </div>
);

提升State層級

在 React 中,資料傳遞的方式屬於單向資料流,代表所有的資料都只能透過 props,從父層 Component 往子層 Component 傳遞。但當我們希望不同的 Component 間可以共享某些數據,比如接下來想要在 Member Component 裡,顯示成員成績的狀態是合格還是不及格,就需要共享 Counter Component 內的 Score State。

解決的方法就是提升 State 的層級,將共享的資料提升到組件匯流的最上層,舉例來說如下圖,B 需要共享 C 的 Data State,那我們就將 State 提升到父層組件 A,再透過 Props 傳遞 Data 給 B 和 C。

在我們的範例中,設定分數大於零就顯示 PASS、否則顯示 FAIL,Member Component 就需要共享 Counter Component 內的 Score State。所以將 State 提升到最上層的組件 Member Component,並將資料透過 Props 傳遞給 Counter Compenent。

Member.js

import { useState } from "react";
import Counter from "./Counter";

export default function Member(props) {
  const [score, setScore] = useState(0);

  return (
    <div className="member">
      <h2>{props.name}</h2>
      <div>{score > 0 ? "PASS" : "FAIL"}</div>
      <Counter score={score}/>
    </div>
  );
}

問題是 Props 是唯讀的,不能在子組件內修改父層 State 的值,那要如何修改計數器按鈕 onClick 所觸發的動作?我們可以透過 Props 傳遞動作,先在 Member Component 設定好按下按鈕後,修改 Score State 的動作handleScoreChange,再將函式作為 Props 傳遞給 Counter Component 呼叫。

Member.js

export default function Member(props) {
  ...
  
  function handleScoreChange(number) {
    //接收分數加減完成後的number
    setScore(number);
  }

  return (
    <div className="member">
      ...
      <Counter score={score} handleScoreChange={handleScoreChange}/>
    </div>
  );
}

Counter.js

export default function Counter(props) {
  return (
    <div className="Counter">
      ...
      <button onClick={() => props.handleScoreChange(props.score + 1)}>
        Plus
      </button>
      {props.score > 0 && (
        <button onClick={() => props.handleScoreChange(props.score - 1)}>
          minus
        </button>
      )}
    </div>
  );
}

分數大於零就顯示PASS、否則顯示FAIL


小結

學會了 React 其他的功能,包括如何有條件的 Render Element、用 map() 自動生成列表和提升 State 層級,React 本身的介紹差不多就告一個段落了。當你從單純撰寫 HTML、CSS、JS,慢慢轉變為在專案中套入框架,用框架的模式去思考,就會體會到這種模組化的方式可以幫助我們,讓專案更好開發與管理。接下來就會進一步提到如何使用 Redux,能夠更好的管理應用程式中的資料狀態。

範例程式碼

如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️

參考資料


上一篇
[Day 20 - React] 網頁UI組件化 — React component
下一篇
[Day 22 - Redux] 有了Redux,狀態管理沒煩惱
系列文
Follow me! 來一場前端技能樹之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言